package com.congee02.autocrud.service;

import com.congee02.autocrud.exception.service.SpecificIdEntityNotExistException;
import com.congee02.autocrud.util.EntityUtils;
import com.congee02.autocrud.util.GenericUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.util.Assert;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;

/**
 * 抽象CRUD业务类
 * @param <T> JavaBean 类型
 */
public abstract class AbstractBaseCRUDService<T> {

    // 泛型的类型
    private final Class clazz;
    // id 所对应的成员对象
    private final Field idField;
    // id Getter
    private final Method idGetterMethod;
    // id Setter
    private final Method idSetterMethod;
    // CRUD Service
    private final JpaRepository<T, Long> repository;


    protected AbstractBaseCRUDService(JpaRepository<T, Long> repository) {
        this.repository = repository;
        this.clazz = GenericUtils.getSingleGenericType(this.getClass());
        this.idField = EntityUtils.getEntityIdField(this.clazz);
        this.idGetterMethod = EntityUtils.getEntityIdGetterMethod(this.clazz);
        this.idSetterMethod = EntityUtils.getEntityIdSetterMethod(this.clazz);
    }

    protected Long getEntityId(T entity) {
        Assert.notNull(entity, "The entity must be not null");
        Long id = null;
        try {
            id = (Long) idGetterMethod.invoke(entity);
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return id;
    }

    protected void setEntityId(T entity, Long id) {
        Assert.notNull(entity, "The entity must be not null");
        try {
            idSetterMethod.invoke(entity, id);
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    protected Class getClazz() {
        return this.clazz;
    }

    public T insert(T entity) {
        setEntityId(entity, null);
        return repository.save(entity);
    }

    public T update(T entity) {
        final boolean exist = entityExistInTable(entity);
        if (! exist) {
            throw new SpecificIdEntityNotExistException(clazz, getEntityId(entity));
        }
        return repository.save(entity);
    }

    public void deleteById(Long id) {
        final boolean exist = entityExistInTable(id);
        if (! exist) {
            throw new SpecificIdEntityNotExistException(clazz, id);
        }
        repository.deleteById(id);
    }

    public T findById(Long id) {
        final Optional<T> byId = repository.findById(id);
        if (byId.isEmpty()) {
            throw new SpecificIdEntityNotExistException(clazz, id);
        }
        return byId.get();
    }

    public Page<T> findByPage(int pageIndex, int pageSize) {
        PageRequest request = PageRequest.of(pageIndex, pageSize);
        Page<T> page = repository.findAll(request);
        return page;
    }

    private boolean entityExistInTable(T entity) {
        final Long entityId = getEntityId(entity);
        return entityExistInTable(entityId);
    }

    private boolean entityExistInTable(Long id) {
        final Optional<T> byId = repository.findById(id);
        return byId.isPresent();
    }

}
